package net.callumtaylor.swipetorefresh.helper;
import net.callumtaylor.swipetorefresh.view.OnOverScrollListener;
import net.callumtaylor.swipetorefresh.view.RefreshableListView;
import net.callumtaylor.swipetorefresh.view.RefreshableScrollView;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
/**
* This is the refresh delegate responsible for handling the touch events
* from the {@link RefreshableListView} and {@link RefreshableScrollView}
* classes.
*
* Some code in {@link RefreshDelegate#onTouch(View, MotionEvent)} has been
* used from <a href="https://github.com/chrisbanes/ActionBar-PullToRefresh">Chris Banes' Library</a>
*/
public class RefreshDelegate
{
private final DisplayMetrics mDM;
private Context mContext;
private ScrollDelegate scrollDelegate;
private OnOverScrollListener onOverScrollListener;
private int refreshCount = 0;
private int mTouchSlop;
private float mInitialMotionY, mLastMotionY;
private boolean mIsBeingDragged, mIsRefreshing, mIsHandlingTouchEvent;
public RefreshDelegate(Context context, ScrollDelegate scrollDelegate)
{
this.scrollDelegate = scrollDelegate;
mContext = context;
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
mDM = new DisplayMetrics();
WindowManager window = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
display.getMetrics(mDM);
}
public void setScrollDeletage(ScrollDelegate delegate)
{
this.scrollDelegate = delegate;
}
public void setOnOverScrollListener(OnOverScrollListener l)
{
this.onOverScrollListener = l;
}
private boolean canRefresh(boolean fromTouch)
{
return !mIsRefreshing && (!fromTouch || onOverScrollListener != null);
}
private void onPull()
{
final float pxScrollForRefresh = Math.min(mDM.heightPixels / 3f, densityPixel(300));
final float scrollLength = mLastMotionY - mInitialMotionY;
if (scrollLength < pxScrollForRefresh)
{
onOverScrollListener.onRefreshScrolledPercentage(scrollLength / pxScrollForRefresh);
}
else
{
refreshCount++;
refresh();
}
}
private void onPullEnded()
{
if (!mIsRefreshing)
{
onOverScrollListener.onReset();
//onRefreshComplete();
}
}
private void onPullStarted()
{
if (onOverScrollListener != null)
{
onOverScrollListener.onBeginRefresh();
}
}
/**
* Resets the refreshable view
*/
public void onRefreshComplete()
{
if (onOverScrollListener != null)
{
//onOverScrollListener.onReset();
}
mIsRefreshing = false;
mIsBeingDragged = false;
resetTouch();
}
public boolean onTouch(View view, MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0)
{
return false;
}
switch (event.getAction())
{
case MotionEvent.ACTION_MOVE:
{
if (refreshCount > 0) return false;
if (mIsRefreshing)
{
if (!scrollDelegate.isScrolledToTop())
{
resetTouch();
}
return false;
}
final float y = event.getY();
// As there are times when we are not given the ACTION_DOWN, we
// need to check here
// whether we should handle the event
if (!mIsHandlingTouchEvent)
{
if (canRefresh(true) && scrollDelegate.canStartRefreshing())
{
mIsHandlingTouchEvent = true;
mInitialMotionY = y;
}
else
{
// We're still not handling the event, so fail-fast
return false;
}
}
// We're not currently being dragged so check to see if the user
// has scrolled enough
if (!mIsBeingDragged && (y - mInitialMotionY) > mTouchSlop)
{
mIsBeingDragged = true;
onPullStarted();
}
if (mIsBeingDragged)
{
final float yDx = y - mLastMotionY;
/**
* Check to see if the user is scrolling the right direction
* (down). We allow a small scroll up which is the check
* against negative touch slop.
*/
if (yDx >= -mTouchSlop)
{
onPull();
if (yDx > 0f)
{
mLastMotionY = y;
}
}
else
{
resetTouch();
}
}
else if (!scrollDelegate.isScrolledToTop())
{
onRefreshComplete();
}
break;
}
case MotionEvent.ACTION_DOWN:
{
// If we're already refreshing, ignore
if (canRefresh(true) && scrollDelegate.isScrolledToTop())
{
mIsHandlingTouchEvent = false;
mInitialMotionY = event.getY();
}
else
{
mIsHandlingTouchEvent = false;
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
{
refreshCount = 0;
resetTouch();
break;
}
}
return false;
}
public void fauxRefresh()
{
if (onOverScrollListener != null)
{
mIsRefreshing = true;
}
else
{
onRefreshComplete();
}
}
public void refresh()
{
if (onOverScrollListener != null)
{
mIsRefreshing = true;
onOverScrollListener.onRefresh();
}
else
{
onRefreshComplete();
}
}
public void resetTouch()
{
if (mIsBeingDragged)
{
// We were being dragged, but not any more.
mIsBeingDragged = false;
onPullEnded();
}
mIsHandlingTouchEvent = false;
mInitialMotionY = mLastMotionY = 0f;
if (scrollDelegate != null)
{
scrollDelegate.onResetTouch();
}
}
/**
* Starts a refresh intent
*/
public void startRefresh()
{
refresh();
}
public int densityPixel(int dp)
{
int pixels = (int)(dp * mDM.density);
return pixels;
}
public static interface ScrollDelegate
{
/**
* This is whats called on initial touch if when {@link canStartRefreshing}
* returns true, to begin the refreshing motion
* @return
*/
public boolean isScrolledToTop();
/**
* This is called during the move event to make sure we can still refresh.
* This check should be to ensure that the list is fully at the top before
* any refresh functionality will begin
* @return
*/
public boolean canStartRefreshing();
/**
* Called when the touch event is reset
*/
public void onResetTouch();
}
}